using SharpSoft.Net.P2P.NatModels;
using SharpSoft.Net.Protocols;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SharpSoft.Net.P2P
{
    public class NatClient : IDisposable
    {
        private TcpPeer natpeer;
        public NatClient(string appname, string deviceid, int localport)
        {
            thisappname = appname;
            thisdeviceid = deviceid;
            natpeer = new TcpPeer(localport);
        }
        public NatClient(int localport)
        {
            thisappname = "any_app";
            thisdeviceid = Guid.NewGuid().ToString().ToLower();
            natpeer = new TcpPeer(localport);
        }

        public NatClient() : this(0)
        {
        }

        private void checkConnected()
        {
            if (!natpeer.Connected)
            {
                throw new Exception("Not connected with Hole Punching Service yet");
            }
        }
        public virtual void Dispose()
        {
            natpeer.Dispose();
            ////peerServer?.Dispose();
        }

        //////TcpPeerServer peerServer = null;
        /////// <summary>
        /////// 在相同的端口上启动侦听（如果有必要）
        /////// </summary>
        ////public void ListenTheSamePort()
        ////{
        ////    var peerServer = new TcpPeerServer(natpeer.LocalEndPoint);
        ////    peerServer.Listen();
        ////    peerServer.AcceptTCPPeerWith(p =>
        ////    {
        ////        Console.WriteLine($"{p.RemoteEndPoint}接入。");
        ////    });
        ////}

        public string AppName { get { return this.thisappname; } }
        public string DeviceId { get { return this.thisdeviceid; } }

        private string thisappname;
        private string thisdeviceid;

        public IPEndPoint PublicEndpoint { get; set; }
        /// <summary>
        /// 在Nat成功后是否关闭当前连接
        /// </summary>
        public bool CloseWhenNatSuccess { get; set; } = false;
        public IPEndPoint LocalEndPoint { get { return natpeer.LocalEndPoint; } }

        /// <summary>
        /// 连接到NAT服务
        /// </summary>
        /// <param name="address"></param>
        /// <param name="port"></param>
        public void ConnectToNATService(string address, int port)
        {
            natpeer.Connect(address, port);
            natpeer.ReceiveWith(receiveAsync);

        }
        /// <summary>
        /// 上报自己的App信息、Ip地址以及Nat端口
        /// </summary>
        public void SignIn()
        {
            checkConnected();
            var writer = natpeer.GetWriter();
            writer.WritNetMessagee(new NetMessage()
            {
                Head = new
                {
                    command = "signin",
                    peerid = new PeerInfo() { AppName = this.AppName, DeviceId = this.DeviceId },
                    localep = new IpInfo(natpeer.LocalEndPoint)
                }
            });
        }

        public void SignOut()
        {
            checkConnected();
            var writer = natpeer.GetWriter();
            writer.WritNetMessagee(new NetMessage()
            {
                Head = new
                {
                    command = "signout",
                    peerid = new PeerInfo() { AppName = this.AppName, DeviceId = this.DeviceId },
                    localep = new IpInfo(natpeer.LocalEndPoint)
                }
            });
        }

        private async void receiveAsync(TcpPeer peer, NetMessageStreamReader reader)
        {
            NetMessage msg = null;
            try
            {
                msg = reader.ReadNetMessage();
            }
            catch (Exception)
            {
                this.Dispose();
                return;
            }

#if DEBUG
            Console.WriteLine(msg.Head);
#endif
            var cmd = msg.GetHeadValueString("command");
            if (cmd == "signin")
            {
                var pubep = msg.GetHeadValue<IpInfo>("pubep");
                PublicEndpoint = pubep.ToIPEndPoint();
                this.Signed?.Invoke(this, PublicEndpoint);
            }
            else if (cmd == "want_connect")
            {//收到想要连接的指令
                var otherpeer = msg.GetHeadValue<PeerInfo>("otherpeer");
                var pubep = msg.GetHeadValue<IpInfo>("pubep");
                var priep = msg.GetHeadValue<IpInfo>("priep");

#if DEBUG
                Console.WriteLine($"{otherpeer}]请求连接。");
#endif
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                await NatTo(priep, pubep);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                var writer = natpeer.GetWriter();
                writer.WritNetMessagee(new NetMessage()
                {
                    Head = new
                    {
                        command = "nat_ok",
                        peerid = new PeerInfo() { AppName = this.AppName, DeviceId = this.DeviceId },
                        otherpeer = otherpeer
                    }
                });

            }
            else if (cmd == "nat_ok")
            {//NAT打通成功
                var otherpeer = msg.GetHeadValue<PeerInfo>("otherpeer");
                var pubep = msg.GetHeadValue<IpInfo>("pubep");
                var priep = msg.GetHeadValue<IpInfo>("priep");
                var localpubep = msg.GetHeadValue<IpInfo>("localpubep");
#if DEBUG
                Console.WriteLine($"[{pubep}]通知打洞完成。");
#endif
                NatSuccess?.Invoke(this, priep.ToIPEndPoint(), pubep.ToIPEndPoint());
                if (CloseWhenNatSuccess)
                {
                    this.Dispose();
                }
            }
            else if (cmd == "error")
            {//发生错误
                string errormsg = msg.GetHeadValueString("error");
                NatError?.Invoke(this, errormsg);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="natClient"></param>
        /// <param name="privateEndPoint">远端内网IP</param>
        /// <param name="publicEndPoint">远端外网IP</param>
        public delegate void NatSuccessEventHandler(NatClient natClient, IPEndPoint privateEndPoint, IPEndPoint publicEndPoint);
        /// <summary>
        /// 通知NAT成功
        /// </summary>
        public event NatSuccessEventHandler NatSuccess;
        public delegate void SignedEventHandler(NatClient natClient, IPEndPoint localPublicEndPoint);
        public event SignedEventHandler Signed;

        public delegate void NatErrorEventHandler(NatClient natClient, string errormsg);
        public event NatErrorEventHandler NatError;
        /// <summary>
        /// 打通
        /// </summary>
        /// <param name="address"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        private async Task NatTo(IpInfo priep, IpInfo pubep)
        {
            await Task.Run(delegate
             {
                 TcpPeer tcpPeer = new TcpPeer(natpeer.LocalEndPoint);

                 try
                 {
                     Console.WriteLine($"尝试打洞。对方地址：{pubep}");
                     tcpPeer.TryConnect(pubep.ToIPEndPoint(), 1000);
                     Console.WriteLine("在打洞时直连成功。");
                     //Connected?.Invoke(tcpPeer);
                 }
                 catch (Exception)
                 {
                     Console.WriteLine($"尝试打洞完成。对方地址：{pubep}");
                 }
                 finally
                 {
                     //tcpPeer.Socket.Shutdown(SocketShutdown.Both);
                     //tcpPeer.Close();
                     //tcpPeer.Dispose();
                 }
             });
        }
        /// <summary>
        /// 请求访问其他客户端
        /// </summary>
        /// <param name="appname"></param>
        /// <param name="deviceid"></param>
        public void RequestConnectTo(string appname, string deviceid)
        {
            checkConnected();
            var writer = natpeer.GetWriter();
            writer.WritNetMessagee(new NetMessage()
            {
                Head = new
                {
                    command = "connect_to",
                    peerid = new PeerInfo() { AppName = this.AppName, DeviceId = this.DeviceId },
                    otherpeer = new PeerInfo() { AppName = appname, DeviceId = deviceid },
                }
            });


        }
        ///// <summary>
        ///// 请求关闭连接,并等待连接被关闭
        ///// </summary> 
        //public void RequestClose()
        //{
        //    var writer = this.GetWriter();
        //    writer.WritNetMessagee(new NetMessage() { Head = new { command = "close" } });
        //    //int t = 0;
        //    //while (t < 3000)
        //    //{
        //    //    var status =   this.TestConnectionStatus();
        //    //    if (!status)
        //    //    {
        //    //        break;
        //    //    }
        //    //    Thread.Sleep(50);
        //    //    t += 50;
        //    //}

        //}
    }
}
